#!/usr/local/bin/python3.3
# Author: Botter Eric

import socket, select

class Server:
    __connectedUsers = [] # List of tuples (username, ip) of the currently connected clients 
    __storageFile = 'credentials.csv'
    __socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    __allSockets = [__socket]
    __chatRooms = []
    __BUFFER_SIZE = 4096
    __MAX_CLIENTS = 10
    
    def __init__(self):
        TCP_PORT = 8080
        TCP_IP = socket.gethostbyname(socket.gethostname())
        self.__socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        print('Starting server on address:', TCP_IP, ':', TCP_PORT)
        self.__socket.bind((TCP_IP, TCP_PORT))
        self.__socket.listen(self.__MAX_CLIENTS)
        self.main()

    def __del__(self):
        self.__socket.close()
        
    def send(self, client, message):
        client.send(message.encode('ascii')) # Should check if the client is still connected

    def __getAllUsers(self):
        try:
            f = open(self.__storageFile,"r")
        except FileNotFoundError:
            open(self.__storageFile,"w").close()
            f = open(self.__storageFile,"r")
        temp = [line.split(',') for line in f.readlines()]
        f.close()
        return temp

    def __getUserByIP(self, ip):
        for x in self.__connectedUsers:
            if x[1] == ip:
                return x[0]
        return ''

    def __getIPByUser(self, user):
        for x in self.__connectedUsers:
            if x[0] == user:
                return x[1]
        return ''

    def logUser(self, client, params):
        print('Logging in', params[0], 'on address', client.getsockname(), '...')
        if params in self.__getAllUsers():
            self.send(client, 'LOGIN\tOK')
            self.sendConnectedClients(client)
            self.__connectedUsers += [[params[0], client]]
        else:
            self.send(client, 'LOGIN\tFAIL')
        return b
    
    def sendConnectedClients(self, client):
        self.send(client, 'PEOPLE\t' + '\t'.join([x[0] for x in __connectedUsers]))

    def disconnectClient(self, client, params):
        print('Disconnecting client: ', client.getpeername())
        client.close()
        self.__allSockets.remove(client)
        for c in self.__connectedUsers:
            if client == c[1]:
                self.__connectedUsers.remove(c) # needs testing
                break

    def createChatRoom(self, client, params):
        print('Creating chat room as requested by:', client.getpeername())
        temp = [x for x in self.__chatRooms]
        for target in params:
            if target in temp or self.__getIPByUser(target) == '':
                print('Creating chat room FAILED:', target, 'is either offline or busy')
                self.send(client, 'DEST\tFAIL')
                return
        self.__chatRooms += [self.__getUserByIP(client.getpeername())] + params;
        self.send(client, 'DEST\tOK')

    def leaveChatRoom(self, client, params):
        print('Client',client.getpeername(), 'is leaving its chat room...')
        for room in self.__chatRooms:
            if self.__getUserByIP(client.getpeername()) in room:
                room.remove(self.__getUserByIP(client.getpeername()))
                if len(room) == 0:
                    self.__chatRooms.remove(room)
                break
    
    def sendMessage(self, client, params):
        print('Cilent',client.getpeername(), 'sent a message in its room') # There should be a room identifier
        for room in self.__chatRooms:
            sender = self.__getUserByIP(client.getpeername())
            if sender in room:
                for user in room:
                    if user != sender:
                        self.send(self.__getIPByUser(user), 'CHAT\t'+'\t'.join([sender,params[0]))
            break
    
    def parseCommand(self, client, command):
        operations = {
            'LOGIN' : self.logUser,
            'DEST' : self.createChatRoom,
            'CHAT' : self.sendMessage,
            'LEAVE' : self.leaveChatRoom,
            'LOGOUT' : self.disconnectClient
        }
        temp = command.split('\t')
        if temp[0] in operations:
            operations[temp[0]](client, temp[1:])  # coolest line ever -- also unsafe, but still :D
    
    def main(self):
        while 1:
            readable, writable, problematic = select.select(self.__allSockets, [], [])
            for s in readable:
                if s == self.__socket:
                    conn, addr = self.__socket.accept()
                    print('New incoming connection:', conn.getpeername())
                    self.__allSockets.append(conn)
                else:
                    data = s.recv(self.__BUFFER_SIZE)
                    if not data:
                        self.disconnectClient(s, [])
                        break
                    print("From client", s.getpeername(), "received data:\n\t", data)
                    self.parseCommand(s, data.decode('ascii'))


Server()